"""FlashTreePruner.py
by Peter Balogh (palexanderbalogh@yahoo.com)
August 1, 2007
designed to remove all unnecessary files and folders 
from an exported source code tree of ActionScript files.

First: ONLY USE THIS ON AN EXPORTED TREE. Never use this on your actual project files, since IT DELETES LOTS OF THINGS.

Use Eclipse's context menu to do a team->export on your tree, pick a location safely far from your working project files, and so forth.

Move this script to the folder that contains your exported tree. In my case, I exported my tree to the desktop -- so I put this script on my Desktop too.

Open a terminal window and cd to where the python script (and your tree) can be found. In my case, Desktop.

Invoke the script like so:

python pathwalker.py com com/organic/new_template

Here are the four arguments of this command line:

1) python -- well, you use the python interpreter to execute the script, so the first arg is that executable.

2) the name of this script, obviously.

3) the top of the tree -- really, the name of the top directory. In my case, "com".

4) the folder inside the tree that contains your relevant project files. In the above example, I used a project called "com/organic/new_template" -- that folder contains some loose AS files and some folders containing the rest of my AS files.

The script will (rather verbosely but very quickly) walk through your AS files and make a list of all other files that are needed -- and it will walk through *those* files, too, making note of any dependencies.

At the end of the day, it will go through your entire tree (argument 3) and weed out any and all files that have no link to your project -- and it will prune out empty directories as well.


"""

# note -- we use os.path.normcase
# to automatically convert the slashes from / to \ in windows


import sys, os

allfiles = []
needed_files = []




def walkfun ( list_to_append, current_directory, file_list):
	for fi in file_list:
		f = os.path.join ( current_directory, fi )
		if not os.path.isdir( f ):		
			print "for file " + f 
			list_to_append.append( f )
				
"""
for foldername in sys.argv[ 1: ]:
	foldersize = 0
	folderxmlstring = ""
	os.path.walk( foldername, walkfun, foldername ) # recursively dig into each folder
	folderxmlstring = "<folder name='" + foldername + "' size='" + str( foldersize ) + "' >" + folderxmlstring + "</folder>"
	print "folderxmlstring=", folderxmlstring
	totalxmlstring += folderxmlstring
	totalsize += foldersize
"""

pathtop = sys.argv[ 1 ]

"""
The walk function helps you find all files in a directory tree. It takes a directory name, a callback function, and a data object that is passed on to the callback.
"""

os.path.walk( pathtop, walkfun, allfiles )

print "allfiles = ", allfiles
print "of length ", len(allfiles)

projecttop = sys.argv[ 2 ]

os.path.walk( projecttop, walkfun, needed_files )

print "neededfiles = ", needed_files
print "of length ", len( needed_files )











# convert file-path slashes to AS-style dots
# and remove the .as suffix
# so that we're comparing AS apples to apples
# i.e., AS would say com.utils.event.EventFacade.broadcast()
# so format our filenames thusly.


dotted_allfiles = [ os.path.normcase( unneeded_file.replace("/", ".").replace(".as", "")) for unneeded_file in allfiles  if unneeded_file.count(".as") > 0]

dotted_neededfiles = [ os.path.normcase( needed_file.replace("/", ".").replace(".as", "")) for needed_file in needed_files if needed_file.count(".as") > 0]

print "dotted_allfiles = ", dotted_allfiles
print "dotted_neededfiles = ", dotted_neededfiles

firstfilename = dotted_neededfiles[ 0 ]

print "firstfilename = " + firstfilename

dnf_length_old = len( dotted_neededfiles )
daf_length_old = len( dotted_allfiles )



# two ways to compare each line of code:
# first, if it's an import, is the import's description
# matched by the dotted_file version of the file inside allfiles?
# i.e., do any of the dotted files' names contain the import description?
# second, if it *isn't* an import,
# does it contain the name of a dotted file?


def examinefile(  dottedfilename ):
	print "*****EXAMINING " + dottedfilename
	somefilename = dottedfilename.replace(".", "/") 
	somefilename += ".as"
	somefilename = os.path.normcase( somefilename )
	f = open( somefilename, "r" )
	print "You opened " + f.name
	file_lines_list = f.readlines()
	if len ( file_lines_list ) < 2:
		#print "Didn't open " + somefilename + " right!!!"
		#print "len ( file_lines_list ) = ", len ( file_lines_list )
		#print "And the first 3 lines are ", file_lines_list[0:3]
		# probably you're on a machine that expected one carriage return
		# but found another.
		# so let's assume it blobbed the whole file sans line returns.
		
		file_lines_list = file_lines_list[0].split("\r")
		if len ( file_lines_list ) < 2:
			file_lines_list = file_lines_list[0].split("\n")
		
		
	for line in file_lines_list:
		#remove leading whitespace
		line = line.lstrip()
		
		# now, the first possibility
		# is that this is an import line
		# and is using a star.
		# e.g., import com.utils.*
		# in which case, we remove the star
		# and then cycle through our allfiles
		# and if the file's name contains the import description
		# pull that file out of allfiles
		# and put it into indirectly_needed_files
		
		if line.count("import") > 0:
			if line.find("import") == 0:
				#it's an import statement
				import_desc = line.split( "import")[1].lstrip()
				#print "import_desc = " + import_desc
				if import_desc.find("*") > -1:
					# and it's using a star!
					import_desc = import_desc.split("*")[0]
				else:
					import_desc = import_desc.split(";")[0]
				
				# almost there!
				# is the import_desc inside of the dotted file's name?
				for dotted_file in dotted_allfiles:
					if dotted_file.count( import_desc ) > 0:
						#print "HEY! imPORTing " + dotted_file + " in " + line + " whose import description is " + import_desc
						dotted_allfiles.remove( dotted_file )
						dotted_neededfiles.append( dotted_file )
						#print "length of dotted_allfiles = ", len( dotted_allfiles )
		else:					
			#okay, it's not an import line.
			# but what if it's using a fully-qualified path?
			# e.g., com.utils.events.EventFacade.broadcast()...
			# then we cycle through our known allfiles
			# and see if any of them are mentioned in the code.
			# if they are, add that name
			# to indirectly-needed files
			# and remove it from allfiles
			for dotted_file in dotted_allfiles:
				if line.count( dotted_file ) > 0:
					print "HEY! dotted_file is " + dotted_file + " in " + line
					dotted_allfiles.remove( dotted_file )
					dotted_neededfiles.append( dotted_file )
	f.close()	

for dotted_neededfile in dotted_neededfiles:
	examinefile( dotted_neededfile )

print "length of dotted_neededfiles went from ", dnf_length_old
print "to ", len( dotted_neededfiles )

dotted_neededfiles.sort()

for f in dotted_neededfiles:
	print f

print "length of dotted_allfiles went from ", daf_length_old
print "to ", len( dotted_allfiles )

dotted_allfiles.sort()

for f in dotted_allfiles:
	print f

allfiles = [ os.path.normcase( file.replace(".", "/") + ".as" ) for file in dotted_allfiles]



def killfunction ( list_to_append, current_directory, file_list):
	for fi in file_list:
		f = os.path.join ( current_directory, fi )
		if not os.path.isdir( f ):		
			if allfiles.count( f ) > 0:
				print "killing file " + f 
				os.remove( f )
		else:
		#It's a directory, so try to remove it
			try:
				os.removedirs( f )
			except:
				print "Can't remove non-empty dir " + f

os.path.walk( pathtop, killfunction, allfiles )
# that emptied the folders; now delete 'em
os.path.walk( pathtop, killfunction, allfiles )






